import constants from '@/constants';
import { BaseService } from '@/core/base.service';
import { OrderListDto, OrderPayDto, OrderQueryDto } from '@/dtos';
import {
  OrderStatusEnum,
  OrderTypeEnum,
  TokenLogTypeEnum,
  TokenSourceEnum,
  TokenStatusEnum,
  BillStatusEnum,
  BillTypeEnum,
} from '@/enums';
import { compareHash, getCode, halt } from '@/utils';
import { InjectQueue } from '@nestjs/bull';
import { Injectable } from '@nestjs/common';
import { Order, Prisma } from '@prisma/client';
import { Queue } from 'bull';
import dayjs from 'dayjs';
import { sleep } from 'radash';
import { SettingService } from './setting.service';

@Injectable()
export class OrderService extends BaseService {
  constructor(
    @InjectQueue('order')
    private readonly orderQueue: Queue,
    private readonly settingService: SettingService,
  ) {
    super();
  }

  async getList({ userId, params, sort }: OrderListDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
      SELECT #{fields} FROM \`Order\` AS t            
      LEFT JOIN \`User\` AS u ON t.ownerId = u.id
      LEFT JOIN \`Asset\` AS a ON t.assetId = a.id
      WHERE t.isDeleted = false
        ${
          !!where.status && Number(where.status) === OrderStatusEnum.SOLD
            ? `AND (SELECT COUNT(1) FROM SubOrder AS so WHERE so.orderId = t.id AND so.sellerId = ${userId}) > 0`
            : `AND t.ownerId = ${userId}`
        }
        ${where.no ? `AND t.no LIKE '%${where.no}%'` : ''}
        ${
          !!where.status &&
          Number(where.status) > 0 &&
          Number(where.status) !== OrderStatusEnum.SOLD
            ? `AND t.status = ${where.status}`
            : `AND t.status = ${OrderStatusEnum.COMPLETED}`
        }
        ${where.assetTitle ? `AND a.title LIKE '%${where.assetTitle}%'` : ''}
        ${where.startTime ? `AND u.createdAt >= '${where.startTime}'` : ''}
        ${where.endTime ? `AND u.createdAt <= '${where.endTime}'` : ''}
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Order[]>(
      `${sql.replace(
        '#{fields}',
        `
          t.id, t.no, t.fee, t.expireTime, t.createdAt, t.status,
         
          ${
            !!where.status && Number(where.status) === OrderStatusEnum.SOLD
              ? `( 
                  SELECT COUNT(s.id) FROM SubOrder AS s WHERE s.orderId = t.id AND s.sellerId = ${userId}
                ) AS num,`
              : `(
                  SELECT COUNT(s.id) FROM SubOrder AS s WHERE s.orderId = t.id
                ) AS num,`
          }
          ${
            !!where.status && Number(where.status) === OrderStatusEnum.SOLD
              ? `(
                  SELECT SUM(s.price) FROM SubOrder AS s WHERE s.orderId = t.id AND s.sellerId = ${userId}
                ) AS total,`
              : 't.total,'
          }
          t.payType, t.payNo, t.payUrl, t.payTime,
          t.ownerId, a.id AS assetId, a.title AS assetTitle, a.image AS assetImage
        `,
      )}        
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.createdAt DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o) => ({
        ...o,
        total: Number(o.total),
        fee: Number(o.fee),
      })),
    };
  }

  async query({ params, sort }: OrderQueryDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
      SELECT #{fields} FROM \`Order\` AS t
      LEFT JOIN \`User\` AS u ON t.ownerId = u.id
      LEFT JOIN \`Asset\` AS a ON t.assetId = a.id
      WHERE t.isDeleted = false
        ${!!where.no ? `AND t.no LIKE '%${where.no}%'` : ''}
        ${!!where.type ? `AND t.type = ${where.type}` : ''}
        ${!!where.status ? `AND t.status = ${where.status}` : ''}
        ${!!where.assetId ? `AND t.assetId = ${where.assetId}` : ''}
        ${!!where.realname ? `AND u.realname LIKE '%${where.realname}%'` : ''} 
        ${!!where.mobile ? `AND u.mobile LIKE '%${where.mobile}%'` : ''}
        ${!!where.startTime ? `AND CONVERT_TZ(t.createdAt, 'UTC', 'Asia/Shanghai') >= '${where.startTime}'` : ''}
        ${!!where.endTime ? `AND CONVERT_TZ(t.createdAt, 'UTC', 'Asia/Shanghai') <= '${where.endTime}'` : ''}
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Order[]>(
      `${sql.replace(
        '#{fields}',
        `
          t.id, t.no, t.total, t.fee, t.num, t.payTime, t.createdAt, t.type, t.status,
          t.ownerId, u.realname, u.mobile, a.title AS assetTitle, a.image AS assetImage
        `,
      )}
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.createdAt DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o) => ({
        ...o,
        total: Number(o.total),
        fee: Number(o.fee),
      })),
    };
  }

  async findOne(
    where: Prisma.OrderWhereUniqueInput,
    select?: Prisma.OrderSelect,
  ): Promise<Order | null> {
    return this.prisma.order.findUnique({
      where,
      select,
    });
  }

  async getSubOrders(no: string) {
    const orders = await this.prisma.subOrder.findMany({
      where: {
        order: {
          no,
        },
      },
      select: {
        id: true,
        price: true,
        fee: true,
        token: {
          select: {
            no: true,
          },
        },
        buyer: {
          select: {
            id: true,
            realname: true,
            mobile: true,
          },
        },
        seller: {
          select: {
            id: true,
            realname: true,
            mobile: true,
          },
        },
      },
    });
    return orders.map((o) => ({
      ...o,
      price: Number(o.price),
      fee: Number(o.fee),
    }));
  }

  async getOrderByNo(no: string) {
    const order = await this.prisma.order.findFirst({
      where: {
        no,
        isDeleted: false,
      },
      select: {
        id: true,
        no: true,
        total: true,
        expireTime: true,
        payType: true,
        payNo: true,
        payUrl: true,
        payTime: true,
        num: true,
        type: true,
        status: true,
        createdAt: true,
        owner: {
          select: {
            id: true,
          },
        },
        asset: {
          select: {
            id: true,
            title: true,
            image: true,
          },
        },
        plan: {
          select: {
            id: true,
          },
        },
        subOrders: {
          select: {
            token: {
              select: {
                no: true,
              },
            },
          },
          take: 100,
        },
      },
    });
    if (!order) {
      return halt('订单信息异常');
    }
    if (
      [OrderStatusEnum.PENDING, OrderStatusEnum.CANCELED].includes(order.status)
    ) {
      delete order.subOrders;
    }
    return {
      ...order,
      total: Number(order.total),
    };
  }

  async cancelOrderByNo(no: string, ownerId: number) {
    const order = await this.prisma.order.findUnique({
      where: {
        no,
        ownerId,
        isDeleted: false,
      },
    });
    if (!order) {
      return halt('订单信息异常');
    }
    await this.orderQueue.add('cancel', {
      no: order.no,
    });
    await sleep(2000);
    return true;
  }

  async completeOrderByNo(no: string) {
    return await this.prisma.$transaction(async (tx) => {
      const order = await tx.order.findUnique({
        where: {
          no,
          status: OrderStatusEnum.PENDING,
          isDeleted: false,
        },
      });
      if (!order) {
        return halt('订单信息异常');
      }
      const subOrders = await tx.subOrder.findMany({
        where: {
          orderId: order.id,
        },
        select: {
          id: true,
          price: true,
          fee: true,
          token: {
            select: {
              id: true,
              no: true,
              price: true,
              classId: true,
            },
          },
          buyer: {
            select: {
              id: true,
              mobile: true,
              account: true,
            },
          },
          seller: {
            select: {
              id: true,
              mobile: true,
              account: true,
            },
          },
        },
      });
      for (let o of subOrders) {
        await tx.token.update({
          where: {
            id: o.token.id,
          },
          data: {
            bidPrice: Number(o.price),
            bidAt: new Date(),
            ownerId: o.buyer.id,
            source: TokenSourceEnum.TRADE,
            status: TokenStatusEnum.HOLD,
          },
        });
        await tx.tokenLog.create({
          data: {
            log: `买入${o.price}￥`,
            type: TokenLogTypeEnum.TRANSFER,
            tokenId: o.token.id,
            fromId: o.seller.id,
            recipientId: o.buyer.id,
          },
        });
      }
      if (order.type === OrderTypeEnum.INITIAL) {
        await tx.asset.update({
          where: {
            id: order.assetId,
          },
          data: {
            circulation: {
              increment: order.num,
            },
          },
        });
      }
      await tx.order.update({
        where: {
          id: order.id,
        },
        data: {
          payTime: new Date(),
          status: OrderStatusEnum.COMPLETED,
        },
      });
    });
  }

  async pay({ no, pass }: OrderPayDto, ownerId: number) {
    const setting = await this.settingService.getDefault();

    return await this.redlock.using([`pay:${ownerId}`], 10000, {}, async () => {
      return await this.prisma.$transaction(async (tx) => {
        const user = await tx.user.findUnique({
          where: { id: ownerId },
        });
        if (!user) {
          return halt('用户信息异常');
        }
        if (!user.txPass) {
          return halt('请先设置操作密码');
        }
        if (!(await compareHash(pass, user.txPass))) {
          return halt('密码错误');
        }

        const order = await tx.order.findUnique({
          where: { no, status: OrderStatusEnum.PENDING },
        });
        if (!order) {
          return halt('订单信息异常');
        }

        if (!this.config.get('isUseBalance')) {
          return order.payUrl;
        }

        const total = Number(order.total);
        const fee = Number(order.total) * Number(setting.fee);

        if (total > Number(user.balance)) {
          return halt('余额不足');
        }

        const subOrders = await tx.subOrder.findMany({
          where: {
            orderId: order.id,
            buyerId: user.id,
          },
          select: {
            id: true,
            price: true,
            fee: true,
            token: {
              select: {
                id: true,
                no: true,
                price: true,
                classId: true,
                asset: true,
              },
            },
            seller: {
              select: {
                id: true,
                mobile: true,
                account: true,
                version: true,
              },
            },
            buyer: {
              select: {
                id: true,
                mobile: true,
                account: true,
                version: true,
              },
            },
          },
        });
        for (let o of subOrders) {
          const fee = Number(o.fee);
          const amount = Number(o.price) - Number(o.fee);
          await tx.token.update({
            where: {
              id: o.token.id,
            },
            data: {
              bidPrice: Number(o.price),
              bidAt: new Date(),
              ownerId: user.id,
              source: TokenSourceEnum.TRADE,
              status: TokenStatusEnum.HOLD,
            },
          });
          const seller = await tx.user.findUnique({
            where: {
              id: o.seller.id,
            },
          });
          const buyer = await tx.user.findUnique({
            where: {
              id: o.buyer.id,
            },
          });
          await tx.user.update({
            where: {
              id: seller.id,
              version: seller.version,
            },
            data: {
              version: {
                increment: 1,
              },
              balance: {
                increment: amount,
              },
            },
          });
          await tx.tokenLog.create({
            data: {
              log: `买入${o.price}￥`,
              type: TokenLogTypeEnum.TRANSFER,
              tokenId: o.token.id,
              fromId: o.seller.id,
              recipientId: user.id,
            },
          });
          await tx.bill.create({
            data: {
              no: await this.generateOrderNo(),
              total: amount,
              fee,
              type: BillTypeEnum.SELL,
              status: BillStatusEnum.COMPLETED,
              ownerId: o.seller.id,
              remark: `卖出《${o.token.asset.title}》#${o.token.no}`,
            },
          });
          await tx.user.update({
            where: {
              id: buyer.id,
              version: buyer.version,
            },
            data: {
              version: {
                increment: 1,
              },
              balance: {
                decrement: Number(o.price),
              },
            },
          });
        }
        if (order.type === OrderTypeEnum.INITIAL) {
          await tx.asset.update({
            where: {
              id: order.assetId,
            },
            data: {
              circulation: {
                increment: order.num,
              },
            },
          });
        }
        await tx.bill.create({
          data: {
            no: await this.generateOrderNo(),
            total,
            fee,
            type: BillTypeEnum.BUY,
            status: BillStatusEnum.COMPLETED,
            ownerId: user.id,
            orderId: order.id,
            remark: `支付订单: ${order.no}`,
          },
        });
        await tx.order.update({
          where: {
            id: order.id,
          },
          data: {
            payTime: new Date(),
            status: OrderStatusEnum.COMPLETED,
          },
        });
        return true;
      });
      // .catch((e) => {
      //   console.log(e);
      //   return halt('支付失败');
      // });
    });
  }

  async generateOrderNo() {
    return `${dayjs().format('YYYYMMDDHHmmss')}${await getCode(11)}`;
  }
}
